#include<iostream>
using namespace std;

/*不好意思，模板名不能重用
template<typename datatype>
class test{
	int datatype;
};
*/

/*复习一下，我们复习如何声明模板友元
template<typename datatype> class bob;
template<typename datatype> void print(datatype& nd);

template<typename datatype>
class blob{
	friend void print<datatype>(datatype& nd);
};
*/

/*
 *
 *
 *
 *
 *重要！虚函数不能定义为模板
 *
 *
 *
 *
 */

/*template member
 *一种情况类模板里面的函数模板
template<typename datatype>
class test{
	private:
		datatype data;
	public:
		test(datatype nd):data(nd){}
		template<typename outtype> void print(outtype& out);
};

template<typename datatype>
template<typename outtype>
void test<datatype>::print(outtype& out){
	out<<data<<endl;
}
*/

/*第二种情况，普通类的函数模板
class test{
	private:
		int data;
	public:
		test(int nd):data(nd){}
		template<typename outtype> void print(outtype& out);
};

template<typename outtype>
void test::print(outtype& out){
	out<<data<<endl;
}
*/
/*
 *模板化的形参会有特殊的初始化规则
 *绝大多数类型转换规则不适用。
 *const匹配与指针和数组，指针和函数之间的转换还存在
 *对于函数和指针的转换可能是想说指针的函数名之间的关系
 *但是这些全部是针对模板类型参数而言的，如果不是模板类型
 *参数，那么之前的转化规则就适用了。
 */

/*
 * 函数指针
void print(){
}
*/


/*
 *书中给出的"妙招"是直接定义两个类型
 */


/*
 *注意，函数模板的实例化是通过参数进行的，如果没有出现在参数中
 *编译器就不能很好的实例化对应的typename 
 
 template<typename type1,typename type2,typename type3>
 type1 print(type2 a,type3 b){
        type1 c;
	return c;
 }
*然而显式指定的参数却又可以使用类型转换机制了
*/

/*
 *更操蛋的是模板的定义与实现不能分开
 */

/*
 *当利用一个函数模板为函数指针赋值的时候，编译器直接使用指针的类型
 *来推断实参。
 */

/*
 *const& T在传入一const实参的时候，const不会出现两次。
 *
 */
/*
template<typename type1>
void print(type1 nt){
	nt=nullptr;
}
**/

/*函数的参数是左值引用(T& a)只能够传递一个左值

template<typename type>
void print(type& nt){
	nt=1.23;
}
*/




/*
 *
 *以下内容十分重要
 *
 */

/*函数的参数是一个const type& 的话，就可以绑定到一个右值上面
template<typename type>
void print(const type& nt){
}
传入一个const int type的类型变为int，而不是const const int&
*/

/*
 *右值引用的重要例外:当将一个左值传递给一个右值引用的时候，参数的推断会
 *发生变化 T&& 传入一个int i=0;那么T的类型直接推断为int& 而不是推断为int
 *这导致右值引用的类型参数行为异常
 

template<typename type>
void print(type&& nt){
	nt=2;
}
*/

/*
 template<typename T>
 void f3(T&& val){
	T t = val;//假设使用了左值(int i)来初始化val，那么T就变成了int& int& t=val(val 也是一个int&)\
	此时两个引用引用一个左值对象。
	t=t+1;//t此时改变了其引用的左值的值
	if(val==t)//两个引用同一个左值的引用总是相等的，if语句内容永远都会执行
		......
}
但是如果我们用右值调用函数，T会被推断为int int t=val就是赋值而不是引用绑定了，此时if语句的判断才有意义。
*/

/*以上内容告诉我们：别随便在模板内搞右值引用*/

int main(){
	//void (*p)()=print;
	//print<int>(2,3);
	/*
	int a=0;
	int *const p=&a;
	*/
	/*
	const int i=1;
	print(i);//实例化的结果void print(const int& nt) 说明底层const被保留了下来
	*/
	
	/*
	 * 编译通过，这本身就不正常。C++规定正常条件下右值引用不能绑定左值。
	 * 但是此时存在一个例外：当右值绑定左值的时候此时直接推断出来的类型为
	 * int&。再加上&&符号，那就是是int& &&nt->int&。所以函数原型为
	 * void print(int& nt)。这就解释了为何改变了数值。。。
	 *引用的折叠规则:X& &  X& &&  X&& & ->X& X&& &&->X&&
	 *底层的const依然被保留

	int i=0;
	print(i);
	cout<<i<<endl;
	*/
}
